package com.zbs.framework.dao.utils.jpa;


import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.util.CollectionUtils;

import javax.persistence.EntityManager;
import java.util.*;
import java.util.function.Consumer;

public class JpaQueryWrapper<T> {

    private final SpecificationHelper<T> specificationHelper = new SpecificationHelper<>();
    private final List<Sort.Order> orderList = new ArrayList<>();
    private final Operator operator = Operator.build();
    private final EntityManager entityManager;
    private final Class<T> clazz;

    public Specification<T> getPacification() {
        return specificationHelper.getPacification(operator);
    }


    public JpaQueryWrapper(Class<T> t,EntityManager em) {
        this.entityManager = em;
        this.clazz = t;
    }

    public List<T> list() {
        this.checkEntityManager();
        return specificationHelper.findResult(entityManager, operator, this.clazz);
    }

    public T get() {
        return this.getOneResult(operator).orElse(null);
    }

    public T getById(Long id) {
        operator.clearCriteria();
        return this.getOneResult(operator.equal("id", id)).orElse(null);
    }

    private Optional<T> getOneResult(Operator params) {
        this.checkEntityManager();
        List<T> result = specificationHelper.findResult(entityManager, params, this.clazz);
        if (!CollectionUtils.isEmpty(result)) {
            if (result.size() > 1) {
                throw new RuntimeException(String.format("query did not return a unique result: %s", result.size()));
            }
            T entity = result.get(0);
            return Optional.ofNullable(entity);
        } else {
            return Optional.empty();
        }
    }

    public Optional<T> getOne() {
        return this.getOneResult(operator);
    }

    public Page<T> page(Integer pageIndex, Integer pageSize) {
        this.checkEntityManager();
        Pageable pageable;
        if (!CollectionUtils.isEmpty(orderList)) {
            pageable = PageUtil.getPageable(pageIndex, pageSize, Sort.by(orderList));
        } else {
            pageable = PageUtil.getPageable(pageIndex, pageSize);
        }
        return specificationHelper.findResult(entityManager, operator, this.clazz, pageable);
    }

    /**
     * 判断entityManager是否注入
     */
    public void checkEntityManager() {
        if (entityManager == null) {
            throw new RuntimeException("error entityManager is null");
        }
    }

//    public Page<T> page(Integer pageIndex, Integer pageSize, Sort sort){
//        if (entityManager == null){
//            throw new RuntimeException("error entityManager is null");
//        }
//        return specificationHelper.findResult(entityManager,operator,this.clazz,PageUtil.getPageable(pageIndex, pageSize, sort));
//    }


    @SafeVarargs
    public final JpaQueryWrapper<T> selectors(SFunction<T, ?>... columns) {
        for (SFunction<T, ?> s : columns) {
            operator.selectors(ColumnUtil.getName(s));
        }
        return this;
    }

    public JpaQueryWrapper<T> desc(String... columns) {
        if (columns.length > 0) {
            for (String column : columns) {
                orderList.add(Sort.Order.desc(column));
            }
        }
        return this;
    }

    @SafeVarargs
    public final JpaQueryWrapper<T> desc(SFunction<T, ?>... columns) {
        if (columns.length > 0) {
            for (SFunction<T, ?> column : columns) {
                orderList.add(Sort.Order.desc(ColumnUtil.getName(column)));
            }
        }
        return this;
    }

    public JpaQueryWrapper<T> asc(String... columns) {
        if (columns.length > 0) {
            for (String column : columns) {
                orderList.add(Sort.Order.asc(column));
            }
        }
        return this;
    }

    @SafeVarargs
    public final JpaQueryWrapper<T> asc(SFunction<T, ?>... columns) {
        if (columns.length > 0) {
            for (SFunction<T, ?> column : columns) {
                orderList.add(Sort.Order.asc(ColumnUtil.getName(column)));
            }
        }
        return this;
    }


    public JpaQueryWrapper<T> or(Consumer<JpaQueryWrapper<T>> function) {
        function.accept(this);
        //辅助or集合
        List<Operator> orCriteria = operator.getOrCriteria();
        //非空判断
        if (!CollectionUtils.isEmpty(orCriteria)){
            List<Operator> copyOrCriteria = new ArrayList<>(orCriteria);
            this.operator.or(copyOrCriteria);
            int size = copyOrCriteria.size();
            for ( int i = 0; i < size; i++) {
                // this.operator.getCriteria() 删除 or 重复添加条件
                this.operator.getCriteria().remove(this.operator.getCriteria().size()- 1) ;
            }
            //用完后清楚辅助or集合
            this.operator.clearOrCriteria();
        }
        return this;
    }

    public JpaQueryWrapper<T> orEq(SFunction<T, ?> column, Object value) {
        return this.orEq(column, value, true);
    }

    public JpaQueryWrapper<T> orEq(SFunction<T, ?> fn, Object value, Boolean flag) {
        operator.orEqual(ColumnUtil.getName(fn), value, flag);
        return this;
    }

    public JpaQueryWrapper<T> eq(SFunction<T, ?> column, Object value) {
        return this.eq(column, value, true);
    }

    public JpaQueryWrapper<T> eq(SFunction<T, ?> column, Object value, Boolean flag) {
        operator.equal(ColumnUtil.getName(column), value, flag);
        return this;
    }

    public JpaQueryWrapper<T> orNe(SFunction<T, ?> column, Object value) {
        return this.orNe(column, value, true);
    }

    public JpaQueryWrapper<T> orNe(SFunction<T, ?> column, Object value, Boolean flag) {
        operator.orNotEqual(ColumnUtil.getName(column), value, flag);
        return this;
    }

    public JpaQueryWrapper<T> ne(SFunction<T, ?> column, Object value) {
        return this.ne(column, value, true);
    }

    public JpaQueryWrapper<T> ne(SFunction<T, ?> column, Object value, Boolean flag) {
        operator.notEqual(ColumnUtil.getName(column), value, flag);
        return this;
    }

    public JpaQueryWrapper<T> orGt(SFunction<T, ?> column, Object value) {
        return this.orGt(column, value, true);
    }

    public JpaQueryWrapper<T> orGt(SFunction<T, ?> column, Object value, Boolean flag) {
        operator.orGt(ColumnUtil.getName(column), value, flag);
        return this;
    }

    public JpaQueryWrapper<T> gt(SFunction<T, ?> column, Object value) {
        return this.gt(column, value, true);
    }

    public JpaQueryWrapper<T> gt(SFunction<T, ?> column, Object value, Boolean flag) {
        operator.gt(ColumnUtil.getName(column), value, flag);
        return this;
    }

    public JpaQueryWrapper<T> orGe(SFunction<T, ?> column, Object value) {
        return this.orGe(column, value, true);
    }

    public JpaQueryWrapper<T> orGe(SFunction<T, ?> column, Object value, Boolean flag) {
        operator.orGe(ColumnUtil.getName(column), value, flag);
        return this;
    }

    public JpaQueryWrapper<T> ge(SFunction<T, ?> column, Object value) {
        return this.ge(column, value, true);
    }

    public JpaQueryWrapper<T> ge(SFunction<T, ?> column, Object value, Boolean flag) {
        operator.ge(ColumnUtil.getName(column), value, flag);
        return this;
    }

    public JpaQueryWrapper<T> orLt(SFunction<T, ?> column, Object value) {
        return this.orLt(column, value, true);
    }

    public JpaQueryWrapper<T> orLt(SFunction<T, ?> column, Object value, Boolean flag) {
        operator.orLt(ColumnUtil.getName(column), value, flag);
        return this;
    }

    public JpaQueryWrapper<T> lt(SFunction<T, ?> column, Object value) {
        return this.lt(column, value, true);
    }

    public JpaQueryWrapper<T> lt(SFunction<T, ?> column, Object value, Boolean flag) {
        operator.lt(ColumnUtil.getName(column), value, flag);
        return this;
    }

    public JpaQueryWrapper<T> orLe(SFunction<T, ?> column, Object value) {
        return this.orLe(column, value, true);
    }

    public JpaQueryWrapper<T> orLe(SFunction<T, ?> column, Object value, Boolean flag) {
        operator.orLe(ColumnUtil.getName(column), value, flag);
        return this;
    }

    public JpaQueryWrapper<T> le(SFunction<T, ?> column, Object value) {
        return this.le(column, value, true);
    }

    public JpaQueryWrapper<T> le(SFunction<T, ?> column, Object value, Boolean flag) {
        operator.le(ColumnUtil.getName(column), value, flag);
        return this;
    }

    public JpaQueryWrapper<T> orLike(SFunction<T, ?> column, Object value) {
        return this.orLike(column, value, true);
    }

    public JpaQueryWrapper<T> orLike(SFunction<T, ?> column, Object value, Boolean flag) {
        operator.orLike(ColumnUtil.getName(column), value, flag);
        return this;
    }

    public JpaQueryWrapper<T> like(SFunction<T, ?> column, Object value) {
        return this.like(column, value, true);
    }

    public JpaQueryWrapper<T> like(SFunction<T, ?> column, Object value, Boolean flag) {
        operator.like(ColumnUtil.getName(column), value, flag);
        return this;
    }

    public JpaQueryWrapper<T> orLeLike(SFunction<T, ?> column, Object value) {
        return this.orLeLike(column, value, true);
    }

    public JpaQueryWrapper<T> orLeLike(SFunction<T, ?> column, Object value, Boolean flag) {
        operator.orLeLike(ColumnUtil.getName(column), value, flag);
        return this;
    }

    public JpaQueryWrapper<T> leLike(SFunction<T, ?> column, Object value) {
        return this.leLike(column, value, true);
    }

    public JpaQueryWrapper<T> leLike(SFunction<T, ?> column, Object value, Boolean flag) {
        operator.leLike(ColumnUtil.getName(column), value, flag);
        return this;
    }

    public JpaQueryWrapper<T> orGeLike(SFunction<T, ?> column, Object value) {
        return this.orGeLike(column, value, true);
    }


    public JpaQueryWrapper<T> orGeLike(SFunction<T, ?> column, Object value, Boolean flag) {
        operator.orGeLike(ColumnUtil.getName(column), value, flag);
        return this;
    }

    public JpaQueryWrapper<T> geLike(SFunction<T, ?> column, Object value) {
        return this.geLike(column, value, true);
    }

    public JpaQueryWrapper<T> geLike(SFunction<T, ?> column, Object value, Boolean flag) {
        operator.geLike(ColumnUtil.getName(column), value, flag);
        return this;
    }

    public JpaQueryWrapper<T> orNotLike(SFunction<T, ?> column, Object value) {
        return this.orNotLike(column, value, true);
    }

    public JpaQueryWrapper<T> orNotLike(SFunction<T, ?> column, Object value, Boolean flag) {
        operator.orNotLike(ColumnUtil.getName(column), value, flag);
        return this;
    }

    public JpaQueryWrapper<T> notLike(SFunction<T, ?> column, Object value) {
        return this.notLike(column, value, true);
    }

    public JpaQueryWrapper<T> notLike(SFunction<T, ?> column, Object value, Boolean flag) {
        operator.notLike(ColumnUtil.getName(column), value, flag);
        return this;
    }


    public JpaQueryWrapper<T> orIsNull(SFunction<T, ?> column) {
        operator.orIsNull(ColumnUtil.getName(column));
        return this;
    }


    public JpaQueryWrapper<T> isNull(SFunction<T, ?> column) {
        operator.isNull(ColumnUtil.getName(column));
        return this;
    }


    public JpaQueryWrapper<T> orIsNotNull(SFunction<T, ?> column) {
        operator.orIsNotNull(ColumnUtil.getName(column));
        return this;
    }

    public JpaQueryWrapper<T> isNotNull(SFunction<T, ?> column) {
        operator.isNotNull(ColumnUtil.getName(column));
        return this;
    }

    public JpaQueryWrapper<T> orGreaterThan(SFunction<T, ?> column, Object value) {
        return this.orGreaterThan(column, value, true);
    }

    public JpaQueryWrapper<T> orGreaterThan(SFunction<T, ?> column, Object value, Boolean flag) {
        operator.orGreaterThan(ColumnUtil.getName(column), value, flag);
        return this;
    }

    public JpaQueryWrapper<T> greaterThan(SFunction<T, ?> column, Object value) {
        return this.greaterThan(column, value, true);
    }

    public JpaQueryWrapper<T> greaterThan(SFunction<T, ?> column, Object value, Boolean flag) {
        operator.greaterThan(ColumnUtil.getName(column), value, flag);
        return this;
    }

    public JpaQueryWrapper<T> orGreaterThanOrEqualTo(SFunction<T, ?> column, Object value) {
        return this.orGreaterThanOrEqualTo(column, value, true);
    }

    public JpaQueryWrapper<T> orGreaterThanOrEqualTo(SFunction<T, ?> column, Object value, Boolean flag) {
        operator.orGreaterThanOrEqualTo(ColumnUtil.getName(column), value, flag);
        return this;
    }

    public JpaQueryWrapper<T> greaterThanOrEqualTo(SFunction<T, ?> column, Object value) {
        return this.greaterThanOrEqualTo(column, value, true);
    }

    public JpaQueryWrapper<T> greaterThanOrEqualTo(SFunction<T, ?> column, Object value, Boolean flag) {
        operator.greaterThanOrEqualTo(ColumnUtil.getName(column), value, flag);
        return this;
    }

    public JpaQueryWrapper<T> orLessThan(SFunction<T, ?> column, Object value) {
        return this.orLessThan(column, value, true);
    }

    public JpaQueryWrapper<T> orLessThan(SFunction<T, ?> column, Object value, Boolean flag) {
        operator.orLessThan(ColumnUtil.getName(column), value, flag);
        return this;
    }

    public JpaQueryWrapper<T> lessThan(SFunction<T, ?> column, Object value) {
        return this.lessThan(column, value, true);
    }

    public JpaQueryWrapper<T> lessThan(SFunction<T, ?> column, Object value, Boolean flag) {
        operator.lessThan(ColumnUtil.getName(column), value, flag);
        return this;
    }

    public JpaQueryWrapper<T> orLessThanOrEqualTo(SFunction<T, ?> column, Object value) {
        return this.orLessThanOrEqualTo(column, value, true);
    }

    public JpaQueryWrapper<T> orLessThanOrEqualTo(SFunction<T, ?> column, Object value, Boolean flag) {
        operator.orLessThanOrEqualTo(ColumnUtil.getName(column), value, flag);
        return this;
    }

    public JpaQueryWrapper<T> lessThanOrEqualTo(SFunction<T, ?> column, Object value) {
        return this.lessThanOrEqualTo(column, value, true);
    }

    public JpaQueryWrapper<T> lessThanOrEqualTo(SFunction<T, ?> column, Object value, Boolean flag) {
        operator.lessThanOrEqualTo(ColumnUtil.getName(column), value, flag);
        return this;
    }


    public JpaQueryWrapper<T> orNotIn(SFunction<T, ?> column, Collection<?> value) {
        operator.orNotIn(ColumnUtil.getName(column), value);
        return this;
    }


    public JpaQueryWrapper<T> notIn(SFunction<T, ?> column, Collection<?> value) {
        operator.notIn(ColumnUtil.getName(column), value);
        return this;
    }

    public JpaQueryWrapper<T> orIn(SFunction<T, ?> column, Collection<?> value, Boolean flag) {
        operator.orIn(ColumnUtil.getName(column), value, flag);
        return this;
    }

    public JpaQueryWrapper<T> in(SFunction<T, ?> column, Collection<?> value) {
        operator.in(ColumnUtil.getName(column), value);
        return this;
    }

    public JpaQueryWrapper<T> orDateBetween(SFunction<T, ?> column, Date[] value) {
        return this.orDateBetween(column, value, true);
    }

    public JpaQueryWrapper<T> orDateBetween(SFunction<T, ?> column, Date[] value, Boolean flag) {
        operator.orDateBetween(ColumnUtil.getName(column), value, flag);
        return this;
    }

    public JpaQueryWrapper<T> dateBetween(SFunction<T, ?> column, Date[] value) {
        this.dateBetween(column, value, true);
        return this;
    }

    public JpaQueryWrapper<T> dateBetween(SFunction<T, ?> column, Date[] value, Boolean flag) {
        operator.dateBetween(ColumnUtil.getName(column), value, flag);
        return this;
    }
}
